﻿using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

using ManagedWin32;
using Socket = ManagedWin32.Win32Socket;

namespace ExampleServer {

    class IOThread : IDisposable {

        private Win32Thread m_thread;

        public IOThread(ThreadStart threadStart) {
            m_thread = new Win32Thread(threadStart);
        }

        public void Dispose() {
            if (m_thread != null) {
                var stopped = m_thread.Join(0);
                while (!stopped) {
                    m_thread.CancelSynchronousIO();
                    stopped = m_thread.Join(1);
                }
                m_thread.Dispose();
                m_thread = null;
            }
        }
    }

    class ExampleUdpServer : IDisposable {

        private IPEndPoint m_listenEndpoint;

        private IOThread m_ioThread;

        public ExampleUdpServer(IPEndPoint listenEndpoint) {
            m_listenEndpoint = listenEndpoint;
            m_ioThread = new IOThread(Run);
        }

        public void Dispose() {
            if (m_ioThread != null) {
                m_ioThread.Dispose();
                m_ioThread = null;
            }
        }

        private void Run() {
            try {
                using (var socket = new Socket(m_listenEndpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp)) {
                    socket.Bind(m_listenEndpoint);
                    var buffer = new byte[4096];
                    while (true) {
                        EndPoint remoteEndPoint = null;
                        int count = socket.ReceiveFrom(buffer, ref remoteEndPoint);
                        var line = Encoding.UTF8.GetString(buffer, 0, count);
                        line = line.ToUpper();
                        var bytes = Encoding.UTF8.GetBytes(line);
                        socket.SendTo(bytes, remoteEndPoint);
                    }
                }
            } catch (SocketException e) {
                if (e.SocketErrorCode != SocketError.Interrupted) {
                    Console.WriteLine(e);
                }
            } catch (Exception e) {
                Console.WriteLine(e);
            }
        }
    }

    class ExampleTcpServer : IDisposable {

        private IPEndPoint m_listenEndpoint;

        private IOThread m_ioThread;

        public ExampleTcpServer(IPEndPoint listenEndpoint) {
            m_listenEndpoint = listenEndpoint;
            m_ioThread = new IOThread(Run);
        }

        public void Dispose() {
            if (m_ioThread != null) {
                m_ioThread.Dispose();
                m_ioThread = null;
            }
        }

        private void ServeOneConnection(Socket listenSocket) {
            try {
                var udpEndPoint = new IPEndPoint(IPAddress.Loopback, m_listenEndpoint.Port);
                using (var tcpSocket = listenSocket.Accept()) {
                    using (var stream = new Win32SocketStream(tcpSocket)) {
                        using (var reader = new StreamReader(stream)) {
                            using (var writer = new StreamWriter(stream)) {
                                writer.WriteLine("Enter a line to convert to uppercase or 'quit' to disconnect");
                                writer.Flush();
                                using (var udpSocket = new Socket(m_listenEndpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp)) {
                                    while (true) {
                                        var line = reader.ReadLine();
                                        if ((line == null) || (line == "quit")) {
                                            break;
                                        }
                                        var bytes = Encoding.UTF8.GetBytes(line);
                                        udpSocket.SendTo(bytes, udpEndPoint);
                                        EndPoint remoteEndPoint = null;
                                        udpSocket.ReceiveFrom(bytes, ref remoteEndPoint);
                                        line = Encoding.UTF8.GetString(bytes);
                                        writer.WriteLine(line);
                                        writer.Flush();
                                    }
                                }
                            }
                        }
                        tcpSocket.Shutdown(SocketShutdown.Send);
                    }
                }
            } catch (SocketException e) {
                if (e.SocketErrorCode == SocketError.Interrupted) {
                    throw;
                }
                Console.WriteLine(e);
            } catch (Exception e) {
                Console.WriteLine(e);
            }
        }

        private void Run() {
            try {
                using (var listenSocket = new Socket(m_listenEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)) {
                    listenSocket.Bind(m_listenEndpoint);
                    listenSocket.Listen(5);
                    while (true) {
                        ServeOneConnection(listenSocket);
                    }
                }
            } catch (SocketException e) {
                if (e.SocketErrorCode != SocketError.Interrupted) {
                    Console.WriteLine(e);
                }
            } catch (Exception e) {
                Console.WriteLine(e);
            }
        }
    }

    class ExampleServer {

        static IPEndPoint ListenEndPoint = new IPEndPoint(IPAddress.Any, 12345);

        static void ExecuteSelfTest() {
            using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) {
                socket.Connect(new IPEndPoint(IPAddress.Loopback, ListenEndPoint.Port));
                using (var stream = new Win32SocketStream(socket)) {
                    using (var reader = new StreamReader(stream)) {
                        using (var writer = new StreamWriter(stream)) {
                            var firstLine = reader.ReadLine();
                            var text = "Hello, World!";
                            writer.WriteLine(text);
                            writer.Flush();
                            var returnedText = reader.ReadLine();
                            if (returnedText != text.ToUpper()) {
                                throw new ApplicationException("Server returned unexpected response '" + returnedText + "'");
                            }
                            writer.WriteLine("quit");
                            writer.Flush();
                            socket.Shutdown(SocketShutdown.Send);
                            if (reader.ReadLine() != null) {
                                throw new ApplicationException("Server did not disconnect after quit command");
                            }
                        }
                    }
                }
            }
        }

        static void Main(string[] args) {
            try {
                using (var udpServer = new ExampleUdpServer(ListenEndPoint)) {
                    using (var tcpServer = new ExampleTcpServer(ListenEndPoint)) {
                        Console.WriteLine("Executing self test");
                        ExecuteSelfTest();
                        Console.WriteLine("Self test passed, press <Enter> to stop");
                        Console.ReadLine();
                    }
                }
            } catch (Exception error) {
                Console.WriteLine("ERROR: " + error);
                Console.WriteLine("Press <Enter> to exit");
                Console.ReadLine();
            }
        }
    }
}
